﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using AutoMapper;

namespace TianYuan.AutoMapper
{
    public static class AutoMapExtensions
    {
        /// <summary>
        /// Converts an object to another using AutoMapper library. Creates a new object of <see cref="TDestination"/>.
        /// There must be a mapping between objects before calling this method.
        /// </summary>
        /// <typeparam name="TDestination">Type of the destination object</typeparam>
        /// <param name="source">Source object</param>
        //public static TDestination MapTo<TDestination>(this object source)
        //{
        //    return Mapper.Map<TDestination>(source);
        //}

        /// <summary>
        /// Execute a mapping from the source object to the existing destination object
        /// There must be a mapping between objects before calling this method.
        /// </summary>
        /// <typeparam name="TSource">Source type</typeparam>
        /// <typeparam name="TDestination">Destination type</typeparam>
        /// <param name="source">Source object</param>
        /// <param name="destination">Destination object</param>
        /// <returns></returns>
        //public static TDestination MapTo<TSource, TDestination>(this TSource source, TDestination destination)
        //{
        //    return Mapper.Map(source, destination);
        //}
        /// <summary>
        /// 同步锁
        /// </summary>
        private static readonly object Sync = new object();

        /// <summary>
        /// 将源对象映射到已有目标对象
        /// </summary>
        /// <typeparam name="TSource">源类型</typeparam>
        /// <typeparam name="TTarget">目标类型</typeparam>
        /// <param name="source">源对象</param>
        /// <param name="target">目标对象</param>
        public static TTarget MapTo<TSource, TTarget>(this TSource source, TTarget target)
        {
            return MapTo<TTarget>(source, target);
        }

        /// <summary>
        /// 将源对象映射到新的目标对象
        /// </summary>
        /// <typeparam name="TTarget">目标类型</typeparam>
        /// <param name="source">源对象</param>
        public static TTarget MapTo<TTarget>(this object source) where TTarget : new()
        {
            return MapTo(source, new TTarget());
        }
        /// <summary>
        /// 将源集合映射到新的目标集合
        /// </summary>
        /// <typeparam name="TTarget">目标元素类型,范例：Sample,不要加List</typeparam>
        /// <param name="source">源集合</param>
        public static List<TTarget> MapToList<TTarget>(this System.Collections.IEnumerable source)
        {
            return MapTo<List<TTarget>>(source);
        }
        #region 私有方法
        /// <summary>
        /// 将源对象映射到目标对象
        /// </summary>
        private static TTarget MapTo<TTarget>(object source, TTarget target)
        {
            if (source == null)
                throw new ArgumentNullException(nameof(source));
            if (target == null)
                throw new ArgumentNullException(nameof(target));
            var sourceType = GetType(source);
            var targetType = GetType(target);
            var map = GetMap(sourceType, targetType);
            if (map != null)
                return Mapper.Map(source, target);
            lock (Sync)
            {
                map = GetMap(sourceType, targetType);
                if (map != null)
                    return Mapper.Map(source, target);
                InitMaps(sourceType, targetType);
            }
            return Mapper.Map(source, target);
        }

        /// <summary>
        /// 获取类型
        /// </summary>
        private static Type GetType(object obj)
        {
            var type = obj.GetType();
            if ((obj is System.Collections.IEnumerable) == false)
                return type;
            if (type.IsArray)
                return type.GetElementType();
            var genericArgumentsTypes = type.GetTypeInfo().GetGenericArguments();
            if (genericArgumentsTypes == null || genericArgumentsTypes.Length == 0)
                throw new ArgumentException("泛型类型参数不能为空");
            return genericArgumentsTypes[0];
        }

        /// <summary>
        /// 获取映射配置
        /// </summary>
        private static TypeMap GetMap(Type sourceType, Type targetType)
        {
            try
            {
                return Mapper.Configuration.FindTypeMapFor(sourceType, targetType);
            }
            catch (InvalidOperationException)
            {
                lock (Sync)
                {
                    try
                    {
                        return Mapper.Configuration.FindTypeMapFor(sourceType, targetType);
                    }
                    catch (InvalidOperationException)
                    {
                        InitMaps(sourceType, targetType);
                    }
                    return Mapper.Configuration.FindTypeMapFor(sourceType, targetType);
                }
            }
        }

        /// <summary>
        /// 初始化映射配置
        /// </summary>
        private static void InitMaps(Type sourceType, Type targetType)
        {
            try
            {
                var maps = Mapper.Configuration.GetAllTypeMaps();
                Mapper.Initialize(config => {
                    ClearConfig();
                    foreach (var item in maps)
                        config.CreateMap(item.SourceType, item.DestinationType);//清空 了所有的在添加一遍，
                    
                    config.CreateMap(sourceType, targetType);//再新增现在所有的配置
                });
            }
            catch (InvalidOperationException)
            {
                Mapper.Initialize(config => {
                    config.CreateMap(sourceType, targetType);
                });
            }
        }

        /// <summary>
        /// 清空配置
        /// </summary>
        private static void ClearConfig()
        {
            var typeMapper = typeof(Mapper).GetTypeInfo();
            var configuration = typeMapper.GetDeclaredField("_configuration");
            configuration.SetValue(null, null, BindingFlags.Static, null, CultureInfo.CurrentCulture);
        }


        #endregion
    }
}
